package ServerRMI;

import java.awt.Point;
import java.rmi.*;
import java.rmi.registry.*;
//import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import shared.*;

class Message {

    public Message() {
    }
    ;
    public int value;
    public boolean active;
}

class Wybor {

    public Wybor() {
    }
    ;
    public int[] value;
    public boolean active;
}

public class RMIServer extends java.rmi.server.UnicastRemoteObject
        implements RMIInterface {

    int CLI_NR = 5;
    private int vecNumber = 0;    // licznik klientow
    int myNumber = 0;             // numer serwera
    private Map map = new Map();
    private CopyOnWriteArrayList<Bullet> bulletsList = new CopyOnWriteArrayList<Bullet>();
    private HashMap<String, Player> hashPlayersList = new HashMap<String, Player>();
    private final double dT = 0.01;
    private VectorClock vClock = new VectorClock(vecNumber++);
    private Message[][] xmessage = new Message[CLI_NR][CLI_NR];
    private Wybor[][] xmessage2 = new Wybor[CLI_NR][CLI_NR];
    int[][] wybor = new int[CLI_NR][CLI_NR];
    private int[] decyzja = new int[CLI_NR];
    boolean xsend = true;
    boolean gettable = false;
    int losowy = -1;
    String whoAmI = "";
    private int MAX_RANDOM = 1000;
    int[] ile = new int[MAX_RANDOM];
    boolean sendRandomOnce = true;
    Random generator = new Random();
    boolean firstStage = false;
    boolean secondStage = false;
    boolean podsumowanie = false;
    boolean start_all = false;

    // sprawdza, czy wystepuje kolizja miedzy dwoma ruchomymi obiektami
    private boolean isCollision(MovingObject a, MovingObject b) {
        if (Math.pow(a.center().getX() - b.center().getX(), 2.)
                + Math.pow(a.center().getY() - b.center().getY(), 2) > Math.pow(a.radius + b.radius, 2)) {
            return false;
        } else {
            return true;
        }
    }

// sprawdza, czy wystepuje kolizja miedzy mapa a ruchomym obiektem
    private boolean isCollisionWithMap(MovingObject a) {
        if (Math.pow(a.center().getX() - map.center().getX(), 2)
                + Math.pow(a.center().getY() - map.center().getY(), 2) < Math.pow(map.radius - a.radius, 2)) {
            return false;
        } else {
            return true;
        }
    }

// sprawdza, czy wystepuje kolizja miedzy mapa a ruchomym obiektem i sprawdza punkt kolizji
    private boolean isCollisionWithMap(MovingObject a, Point point) {
        if (Math.pow(a.center().getX() - map.center().getX(), 2)
                + Math.pow(a.center().getY() - map.center().getY(), 2) < Math.pow(map.radius - a.radius, 2)) {
            return false;
        } else {
            // znajdz punkt stycznosci
            point.setLocation(a.center().getX(), a.center().getY());
            return true;
        }
    }
    // iloczyn skalarny

    private double dot(Point.Double p1, Point.Double p2) {
        return p1.getX() * p2.getX() + p1.getY() * p2.getY();
    }

    // normalizacja wektora
    private Point.Double normalize(Point.Double p) {

        double fLength = Math.sqrt(p.getX() * p.getX() + p.getY() * p.getY());

        if (fLength == 0.0f) {
            return new Point.Double(0., 0.);
        }

        return new Point.Double(p.getX() / fLength, p.getY() / fLength);
    }

    private void GameTimer(final MyGraphicsView graphicsView, final JTable tableWidget, final JLabel decision)// throws RemoteException {
    {

        Iterator xiterb = bulletsList.iterator();
        Iterator xiterp = hashPlayersList.values().iterator();


        while (xiterb.hasNext()) {
            Bullet iterb = (Bullet) xiterb.next();

            // najpierw sprawdz, czy pocisk czasem nie zyje za dlugo
            if (new Date().getTime() - iterb.startTimer >= 4000) {
                // zwieksz o 1 ilosc pociskow playerowi
                iterb.owner.leftAmmo++;
                // usun pocisk z listy
                bulletsList.remove(iterb);
            } else {
                // ustal droge jaka musi przebyc pocisk
                Point.Double dist = new Point.Double(iterb.velocity.getX() * dT, iterb.velocity.getY() * dT);
                iterb.moveBy(dist.getX(), dist.getY());

                // sprawdz kolizje z mapa
                Point point = new Point(0, 0);
                if (isCollisionWithMap(iterb, point)) {
                    Point.Double norm = new Point.Double(map.center().getX() - point.getX(), map.center().getY() - point.getY());
                    norm = normalize(norm);
                    iterb.velocity.setLocation(0.9 * (-2 * dot(iterb.velocity, norm) * norm.getX() + iterb.velocity.getX()),
                            0.9 * (-2 * dot(iterb.velocity, norm) * norm.getY() + iterb.velocity.getY()));
                    iterb.moveBy(-dist.getX(), -dist.getY());
                }

            }

        }

        // na podstawie nacisnietych klawiszy ustal przesuniecie gracza
        xiterp = hashPlayersList.values().iterator();
        while (xiterp.hasNext()) {
            Player iterp = (Player) xiterp.next();

            // sprawdz kolizje pociskow z playerami

            xiterb = bulletsList.iterator();
            while (xiterb.hasNext()) {
                Bullet iterb = (Bullet) xiterb.next();

                if (isCollision(iterb, iterp) && iterb.owner != iterp)// && iterb) ->  isVisible())
                {

                    iterb.owner.fragsNr++;
                    iterb.owner.leftAmmo++;

                    iterp.deathsNr++;
                    iterp.pos.x = iterp.pos.x = 300;
                    iterp.moveLeft = iterp.moveRight = iterp.moveUp = iterp.moveDown = iterp.shoot = false;
                    iterp.leftAmmo = 5;

                    bulletsList.remove(iterb);

                    break;

                }

            }

            // sprawdz kolizje mapy z playerem

            if (iterp.moveDown) {
                iterp.moveBy(0, 1);
                if (isCollisionWithMap(iterp)) {
                    iterp.moveBy(0, -1);
                }
            }
            if (iterp.moveUp) {
                iterp.moveBy(0, -1);
                if (isCollisionWithMap(iterp)) {
                    iterp.moveBy(0, 1);
                }
            }
            if (iterp.moveRight) {
                iterp.moveBy(1, 0);
                if (isCollisionWithMap(iterp)) {
                    iterp.moveBy(-1, 0);
                }
            }
            if (iterp.moveLeft) {
                iterp.moveBy(-1, 0);
                if (isCollisionWithMap(iterp)) {
                    iterp.moveBy(1, 0);
                }
            }
        }


        // rysowanie

        CopyOnWriteArrayList<Player> playersList = new CopyOnWriteArrayList<Player>(hashPlayersList.values());

        graphicsView.playersList = playersList;

        graphicsView.bulletsList = bulletsList;

        DefaultTableModel model = (DefaultTableModel) tableWidget.getModel();


        for (int k = 0; k < tableWidget.getRowCount(); k++) {
            for (int j = 0; j < 3; j++) {
                model.setValueAt("", k, j);
            }
        }

        model.setValueAt("- server -", 0, 0);
        model.setValueAt("-", myNumber, 3);
        for (Player play : playersList) {
            model.setValueAt(play.nick, play.number, 0);
            model.setValueAt(play.fragsNr, play.number, 1);
            model.setValueAt(play.deathsNr, play.number, 2);
        }

        // skompletuj komunikaty o liczebnosci
        if (!firstStage && start_all) {

            RMIPair<Integer, Integer> obj = GetInfo(myNumber);
            if (obj.getFirst() != -1) {

                decyzja[obj.getFirst()] = obj.getSecond();
                model.setValueAt(obj.getSecond(), obj.getFirst(), 3);
            }
            decyzja[myNumber] = losowy;

            // sprawdz, czy mozna juz przeslac swoja tablice z decyzjami
            firstStage = true;
            secondStage = true;
            for (int i = 0; i < vecNumber; i++) {
                if (decyzja[i] <= 0) {
                    firstStage = false;
                    secondStage = false;
                    break;
                }
            }
            if (vecNumber < 2) {
                firstStage = false;
                secondStage = false;
            }
        }

        if (secondStage) {
            // przeslij swoja tablice z decyzjami

            if (xsend) {

                try {
                    SendTableToRestNodes(myNumber, decyzja, whoAmI);
                } catch (RemoteException ex) {
                    Logger.getLogger(RMIServer.class.getName()).log(Level.SEVERE, null, ex);
                }
                wybor[myNumber] = decyzja;
                xsend = false;
                gettable = true;
            }

            if (gettable) {
                RMIPair<Integer, int[]> obj2 = GetTable(myNumber);

                if (obj2.getFirst() != -1) {
                    wybor[obj2.getFirst()] = obj2.getSecond();
                }


                // sprawdz, czy mozna juz wyswietlac
                secondStage = false;
                podsumowanie = true;
                for (int i = 0; i < vecNumber; i++) {
                    for (int j = 0; j < vecNumber; j++) {
                        if (wybor[i][j] <= 0) {
                            secondStage = true;
                            podsumowanie = false;
                            break;
                        }
                    }
                }

            }
        }

        if (podsumowanie)
        {

           /* System.out.println("otrzymalem (nr " + myNumber);
            for (int i = 0; i < vecNumber; i++) {
                System.out.print("wek " + i + ": ");
                for (int j = 0; j < vecNumber; j++) {
                    System.out.print(wybor[i][j] + " ");
                }
                System.out.println("\n");
            }
*/
            // podsumowanie, ktora liczba wystepuje najczesciej

            String label_wynik = "Otrzymano : ";
            int max_index;
            String max_s;
            for (int i = 0; i < vecNumber; i++) {
                for (int j = 0; j < vecNumber; j++) {
                    if (j != myNumber) {
                        ile[wybor[j][i]]++;
                    }
                }

                max_index = 0;
                max_s = Integer.toString(0);
                for (int j = 1; j < MAX_RANDOM; j++) {
                    if (ile[j] > ile[max_index]) {
                        max_index = j;
                        max_s = Integer.toString(j);
                    }
                }

                for (int j = 0; j < MAX_RANDOM; j++) {
                    if (ile[j] == ile[max_index] && j != max_index) {
                        max_s = "X";
                        break;
                    }
                }

                // zaznacz
                label_wynik += max_s + " ";

                for (int j = 0; j < MAX_RANDOM; j++) {
                    ile[j] = 0;
                }

            }
            
            decision.setText(label_wynik);
            podsumowanie = false;

        }

        graphicsView.repaint();
    }

    public VectorClock SendPositions(String nick, boolean moveLeft, boolean moveRight, boolean moveUp,
            boolean moveDown, boolean shoot, int mouseX, int mouseY, VectorClock vector) throws RemoteException {

        hashPlayersList.get(nick).moveLeft = moveLeft;
        hashPlayersList.get(nick).moveRight = moveRight;
        hashPlayersList.get(nick).moveUp = moveUp;
        hashPlayersList.get(nick).moveDown = moveDown;
        hashPlayersList.get(nick).shoot = shoot;

        if (hashPlayersList.get(nick).shoot) {
            if (hashPlayersList.get(nick).leftAmmo > 0) {
                double divider = (mouseY - hashPlayersList.get(nick).pos.y) / (mouseX - hashPlayersList.get(nick).pos.x);
                double inverted = (mouseX < hashPlayersList.get(nick).pos.x) ? Math.PI : 0.0f;    // gdy mysz jest po lewej stronie, dodaj 180 st.
                double m_fAngle = Math.atan(divider) + inverted;

                Point.Double v = new Point.Double(200 * Math.cos(m_fAngle), 200 * Math.sin(m_fAngle));

                Bullet bullet = new Bullet(hashPlayersList.get(nick), hashPlayersList.get(nick).pos, v);
                bulletsList.add(bullet);
                hashPlayersList.get(nick).leftAmmo--;
            }

        }

        RMILogger.add("Zdarzenie : odebranie klawiszy gracza : " + nick);
        vClock.update(vector);
        vClock.increment();
        return vClock;

    }

    public RMIPair<Integer, VectorClock> Login(String nick) throws RemoteException {
        Player play = new Player(new Point.Double(100, 100), hashPlayersList.size() + 1);
        play.nick = nick;

        hashPlayersList.put(nick, play);

        RMILogger.add("Zdarzenie : logowanie gracza : " + nick);

        // zwraca : numer, pod jakim klient moze umiescic swoj licznik w zegarze; stan zegara
        return new RMIPair<Integer, VectorClock>(vecNumber++, vClock);
    }

    public VectorClock Logout(String nick, VectorClock vector) throws RemoteException {
        hashPlayersList.remove(nick);

        RMILogger.add("Zdarzenie : wylogowanie gracza : " + nick);
        vClock.update(vector);
        vClock.increment();
        return vClock;
    }

    public RMIPair<CopyOnWriteArrayList<Player>, VectorClock> GetPlayersPos(VectorClock vector) throws RemoteException {
        RMILogger.add("Zdarzenie : zwracanie pozycji graczy");
        vClock.update(vector);
        vClock.increment();
        return new RMIPair<CopyOnWriteArrayList<Player>, VectorClock>(new CopyOnWriteArrayList<Player>(hashPlayersList.values()), vClock);
    }

    public RMIPair<CopyOnWriteArrayList<Bullet>, VectorClock> GetBulletsPos(VectorClock vector) throws RemoteException {
        RMILogger.add("Zdarzenie : zwracanie pozycji pociskow");
        vClock.update(vector);
        vClock.increment();
        return new RMIPair<CopyOnWriteArrayList<Bullet>, VectorClock>(bulletsList, vClock);
    }

    public RMIServer(final MyGraphicsView graphicsView, final JTable tableWidget, final JLabel decision) throws RemoteException {

        try {
            // Utworzenie rejestru nazw
            Registry reg = LocateRegistry.createRegistry(12345);

            // związanie z obiektem nazwy
            reg.rebind("RMIServer", this);

            for (int i = 0; i < CLI_NR; i++) {
                decyzja[i] = -1;
                for (int j = 0; j < CLI_NR; j++) {
                    xmessage[i][j] = new Message();
                    xmessage[i][j].active = false;
                    xmessage[i][j].value = -1;
                    xmessage2[i][j] = new Wybor();
                    xmessage2[i][j].active = false;
//                    xmessage2[i][j].value = -1;
                }
            }


        } catch (RemoteException e) {
            e.printStackTrace();
            throw e;
        }

        // System.out.println("Aa " + myNumber + " " + losowy);
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {

            public void run() {

                //System.out.println("A " + myNumber + " " + losowy);
                GameTimer(graphicsView, tableWidget, decision);
                /*if (sendRandomOnce) {
                decyzja[myNumber] = losowy;
                // System.out.println("A " + myNumber + " " + losowy);
                sendRandomOnce = false;
                }*/
            }
        }, 0, 10);

    }

    // uzyskal prosbe od uzytkownika user_start, aby wyslac wiadomosc do user_end
    private void SendInfo(int user_start, int user_end, int info) {
        xmessage[user_start][user_end].value = info;
        xmessage[user_start][user_end].active = true;
    }

    private void SendTable(int user_start, int user_end, int[] info) {
        xmessage2[user_start][user_end].value = info;
        xmessage2[user_start][user_end].active = true;
    }

    // zwraca wiadomosc przeznaczona dla uzytkownika 'user' od uzytkownika 'i'
    public RMIPair<Integer, Integer> GetInfo(int user) {

        for (int i = 0; i < vecNumber; i++) {
            // czy uzytkownik 'i' ma cos do przekazania 'user'-owi
            if (xmessage[i][user].active) {
                xmessage[i][user].active = false;
                return new RMIPair<Integer, Integer>(i, xmessage[i][user].value);
            }
        }
        return new RMIPair<Integer, Integer>(-1, -1);
    }

    // zwraca wiadomosc tablicowa przeznaczona dla uzytkownika 'user'
    public RMIPair<Integer, int[]> GetTable(int user) {

        for (int i = 0; i < vecNumber; i++) {
            if (xmessage2[i][user].active) {
                xmessage2[i][user].active = false;
                return new RMIPair<Integer, int[]>(i, xmessage2[i][user].value);
            }
        }
        return new RMIPair<Integer, int[]>(-1, xmessage2[0][0].value);
    }

    // wysyla wiadomosc o swojej liczebnosci do reszty klientow (z wyjatkiem siebie - 'number') 
    public void SendInfoToRestNodes(int number, int info, String whoami) throws RemoteException {

        if ("liar".equals(whoami)) {
            // wysyla klient zdradliwy nie to co otrzymal
            for (int i = 0; i < vecNumber; i++) {
                if (i != number) 
                {
                    SendInfo(number, i, generator.nextInt(MAX_RANDOM));
                }

            }
        } else if ("truthful".equals(whoami)) {
            // wysyla klient uczciwy to co otrzymal
            for (int i = 0; i < vecNumber; i++) {
                if (i != number) 
                {
                    SendInfo(number, i, info);
                }
            }
        }
       // else
         //   System.out.println("fucka");
    }

    // przekaz wektor swoich wynikow do pozostalych
    public void SendTableToRestNodes(int number, int[] info, String whoami) throws RemoteException {

        int[] dec = new int[vecNumber];
        if ("liar".equals(whoami)) {
            // wysyla klient zdradliwy nie to co otrzymal
            for (int i = 0; i < vecNumber; i++) {
                if (i != number) 
                {
                    for (int j = 0; j < vecNumber; j++) {
                        dec[j] = generator.nextInt(MAX_RANDOM);
                    }
                    SendTable(number, i, dec);
                }

            }
        } else if ("truthful".equals(whoami)) {
            // wysyla klient uczciwy to co otrzymal
            for (int i = 0; i < vecNumber; i++) {
                if (i != number) 
                {
                    SendTable(number, i, info);
                }
            }
        }
     //   else
       //     System.out.println("fuck");
    }

    public int GetNodesNr() throws RemoteException {
        return vecNumber;
    }

    // wyslij wszystkim pozostalym klientom
    void Send(int info, String whoami) throws RemoteException {
        whoAmI = whoami;
        SendInfoToRestNodes(myNumber, info, whoami);
        start_all = true;
    }
}
